package io.xxx.walle.task;

import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.TypeReference;
import io.xxx.walle.assistant.Assistant;
import io.xxx.walle.common.Result;
import io.xxx.walle.config.RateLimiterManager;
import io.xxx.walle.room.*;
import io.xxx.walle.wechaty.PushResult;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import okhttp3.*;
import org.apache.ibatis.session.SqlSessionFactory;
import org.redisson.api.RRateLimiter;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.support.TransactionTemplate;
import org.springframework.web.util.UriComponentsBuilder;

import java.io.IOException;
import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicInteger;

@Slf4j
@Service
public class TaskService {

    private static final NumberFormat NUMBER_FORMAT = new DecimalFormat("#,##0.00");

    @Resource
    private RateLimiterManager rateLimiterManager;

    @Resource
    private OkHttpClient okHttpClient;

    @Resource
    private TaskRepository taskRepository;

    @Resource
    private RoomRepository roomRepository;

    @Resource
    private RoomGroupRepository roomGroupRepository;

    @Resource
    private TaskPushRecordRepository pushRecordRepository;

    @Resource
    private JdbcTemplate jdbcTemplate;

    @Resource
    private TransactionTemplate transactionTemplate;

    @Resource
    private RoomMapper roomMapper;

    @Resource
    private SqlSessionFactory sqlSessionFactory;

    @Resource
    private TaskPushRecordService pushRecordService;

    public Page<Task> getPage(Pageable pageable, String name) {
        return taskRepository.findAllByNameLike(pageable, name);
    }

    public Optional<Task> get(Long taskId) {
        return taskRepository.findById(taskId);
    }

    public Long save(Task task) {
        task.validate();
        taskRepository.save(task);
        return task.getId();
    }

    /**
     * 推送消息
     */
    public void push(Task task) {
        ScopeType scopeType = task.getScopeType();
        List<RoomGroup> roomGroups = task.getRoomGroups();
        AtomicInteger i = new AtomicInteger(0);
        switch (scopeType) {
            case INCLUDE -> {
                for (RoomGroup roomGroup : roomGroups) {
                    List<Room> groupRooms = roomRepository.findAllByRoomGroup(roomGroup);
                    push(task, i, groupRooms);
                }
                push(task, i, task.getRooms());
            }
            case EXCLUDE -> {
                List<Room> groupRooms = roomRepository.findAllByRoomGroupNotIn(roomGroups);
                push(task, i, groupRooms);
                List<Long> roomIds = task.getRooms().stream()
                        .map(Room::getId)
                        .toList();
                List<Room> rooms = roomRepository.findAllByIdNotIn(roomIds);
                push(task, i, rooms);
            }
        }
    }

    private void push(Task task, AtomicInteger i, List<Room> groupRooms) {
        for (Room room : groupRooms) {
            push(task, room);
            i.incrementAndGet();
            updateProcess(task, i.get());
        }
    }

    private void updateProcess(Task task, int i) {
        if (i % 1_000 == 0) {
            double process = 1.0 * i / task.getRoomCount();
            task.setProcess(process);
            taskRepository.save(task);
            log.info("消息[{}:{}]推送进度[{}%]", task.getId(), task.getName(), NUMBER_FORMAT.format(process));
        }
    }

    private void push(Task task, Room room) {
        // TODO 调用NodeJS发送消息
        boolean existed = pushRecordRepository.existsByTaskAndRoom(task, room);
        if (!existed) {
            Assistant assistant = room.getAssistant();
            String url = UriComponentsBuilder.newInstance()
                    .scheme("http")
                    .host(assistant.getHost())
                    .port(assistant.getPort())
                    .path("/say")
                    .toUriString();

            RequestBody requestBody = RequestBody.create("", MediaType.get("application/json; charset=utf-8"));
            Request request = new Request.Builder()
                    .url(url)
                    .post(requestBody)
                    .build();

            RRateLimiter rateLimiter = rateLimiterManager.get(assistant.getOutId());
            rateLimiter.acquire();
            try (Response response = okHttpClient.newCall(request).execute()) {
                ResponseBody responseBody = response.body();
                if (responseBody != null) {
                    Result<PushResult> result = JSON.parseObject(responseBody.string(), new TypeReference<Result<PushResult>>() {
                    });
                    pushRecordService.addTaskPushRecord(task, room, result.getData());
                }
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    }
}
